{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# AI第五周作业——基于用户的协同过滤"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import math"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>user</th>\n",
       "      <th>song</th>\n",
       "      <th>play_count</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>d6589314c0a9bcbca4fee0c93b14bc402363afea</td>\n",
       "      <td>SOADQPP12A67020C82</td>\n",
       "      <td>5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>d6589314c0a9bcbca4fee0c93b14bc402363afea</td>\n",
       "      <td>SOAFTRR12AF72A8D4D</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>d6589314c0a9bcbca4fee0c93b14bc402363afea</td>\n",
       "      <td>SOANQFY12AB0183239</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>d6589314c0a9bcbca4fee0c93b14bc402363afea</td>\n",
       "      <td>SOAYATB12A6701FD50</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>d6589314c0a9bcbca4fee0c93b14bc402363afea</td>\n",
       "      <td>SOBOAFP12A8C131F36</td>\n",
       "      <td>5</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                       user                song  play_count\n",
       "0  d6589314c0a9bcbca4fee0c93b14bc402363afea  SOADQPP12A67020C82           5\n",
       "1  d6589314c0a9bcbca4fee0c93b14bc402363afea  SOAFTRR12AF72A8D4D           1\n",
       "2  d6589314c0a9bcbca4fee0c93b14bc402363afea  SOANQFY12AB0183239           1\n",
       "3  d6589314c0a9bcbca4fee0c93b14bc402363afea  SOAYATB12A6701FD50           1\n",
       "4  d6589314c0a9bcbca4fee0c93b14bc402363afea  SOBOAFP12A8C131F36           5"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 读入数据\n",
    "df_music = pd.read_csv('triplet_dataset_sub_5000_song.csv') \n",
    "df_music.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>play_count</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>count</th>\n",
       "      <td>5.276111e+06</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>mean</th>\n",
       "      <td>2.358383e+00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>std</th>\n",
       "      <td>1.622074e+00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>min</th>\n",
       "      <td>1.000000e+00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>25%</th>\n",
       "      <td>1.000000e+00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>50%</th>\n",
       "      <td>2.000000e+00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>75%</th>\n",
       "      <td>4.000000e+00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>max</th>\n",
       "      <td>5.000000e+00</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "         play_count\n",
       "count  5.276111e+06\n",
       "mean   2.358383e+00\n",
       "std    1.622074e+00\n",
       "min    1.000000e+00\n",
       "25%    1.000000e+00\n",
       "50%    2.000000e+00\n",
       "75%    4.000000e+00\n",
       "max    5.000000e+00"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df_music.describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 将表格转换成稀疏矩阵"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(99894, 6940)"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from scipy.sparse import coo_matrix\n",
    "\n",
    "small_set = df_music\n",
    "user_codes = small_set.user.drop_duplicates().reset_index()\n",
    "song_codes = small_set.song.drop_duplicates().reset_index()\n",
    "user_codes.rename(columns={'index':'user_index'}, inplace=True)\n",
    "song_codes.rename(columns={'index':'song_index'}, inplace=True)\n",
    "song_codes['song_index_value'] = list(song_codes.index)\n",
    "user_codes['user_index_value'] = list(user_codes.index)\n",
    "small_set = pd.merge(small_set,song_codes,how='left')\n",
    "small_set = pd.merge(small_set,user_codes,how='left')\n",
    "mat_candidate = small_set[['user_index_value','song_index_value','play_count']]\n",
    "\n",
    "data_array = mat_candidate.play_count.values\n",
    "row_array = mat_candidate.user_index_value.values\n",
    "col_array = mat_candidate.song_index_value.values\n",
    "\n",
    "data_sparse = coo_matrix((data_array, (row_array, col_array)),dtype=float)\n",
    "data_sparse.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "data_sparse_arr = data_sparse.toarray()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 找出打分最多前4首歌曲"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "import copy\n",
    "\n",
    "def findMostRateSong(arr, songNum, commonNum):\n",
    "\n",
    "    songRate = [0 for i in range(songNum)]\n",
    "    \n",
    "    for i in range(len(arr)):\n",
    "        #for j in range(len(arr[0])):\n",
    "        for j in range(songNum):\n",
    "            if arr[i][j] > 0.1:\n",
    "                songRate[j] += 1\n",
    "    \n",
    "    songRateSort= copy.copy(songRate)\n",
    "    songRateSort.sort(reverse = True)                 \n",
    "    #print(songRate)\n",
    "    #print(songRateSort)\n",
    "                       \n",
    "    MostRateSongCount = [songRateSort[i] for i in range(commonNum)]\n",
    "                       \n",
    "    MostRateSongID = []\n",
    "   \n",
    "    for i in range(len(songRate)):\n",
    "        for j in range(len(MostRateSongCount)):\n",
    "            if songRate[i] == MostRateSongCount[j]:\n",
    "                MostRateSongID.append(i)           \n",
    "                   \n",
    "    return MostRateSongID, MostRateSongCount                           "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "MostRateSongID: [29, 680, 707, 1044]\n",
      "MostRateSongCount: [18626, 17635, 15138, 14945]\n"
     ]
    }
   ],
   "source": [
    "MostRateSongID , MostRateSongCount = findMostRateSong(data_sparse_arr, data_sparse_arr.shape[1], 4)\n",
    "print('MostRateSongID:', MostRateSongID)\n",
    "print('MostRateSongCount:', MostRateSongCount)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 找出给4首歌曲打分的的用户和分数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "def findMostSongUserID(arr, MostRateSongID):\n",
    "    \n",
    "    UserRateID = []\n",
    "    SongRate = []\n",
    "    \n",
    "    for i in range(len(arr)):\n",
    "        isRate = True\n",
    "        uSongRate = []\n",
    "        for j in range(len(MostRateSongID)):\n",
    "            if arr[i][j] < 0.1:\n",
    "                isRate = False\n",
    "            else:\n",
    "                uSongRate.append(arr[i][j])\n",
    "        if isRate:\n",
    "            SongRate.append(uSongRate)\n",
    "            UserRateID.append(i)\n",
    "            \n",
    "    return UserRateID, SongRate"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "UserRateID: [0, 19491, 22276, 30362, 31715, 61768, 70009, 76798, 80790, 88261, 98161]\n",
      "SongRate: [[5.0, 1.0, 1.0, 1.0], [1.0, 1.0, 1.0, 4.0], [1.0, 5.0, 1.0, 1.0], [1.0, 3.0, 1.0, 1.0], [1.0, 1.0, 2.0, 1.0], [3.0, 1.0, 2.0, 5.0], [1.0, 1.0, 3.0, 1.0], [1.0, 2.0, 5.0, 5.0], [5.0, 1.0, 2.0, 1.0], [2.0, 5.0, 5.0, 5.0], [1.0, 1.0, 3.0, 3.0]]\n"
     ]
    }
   ],
   "source": [
    "UserRateID, SongRate = findMostSongUserID(data_sparse_arr, MostRateSongID)\n",
    "print('UserRateID:',UserRateID)\n",
    "print('SongRate:',SongRate)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 计算出这些用户打分的Pearson相关系数并获得系数大于0.7的打分分数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [],
   "source": [
    "def UserFilter(UserASongRate, UserBSongRate):\n",
    "    \n",
    "    UserARateSum = 0\n",
    "    for i in range(len(UserASongRate) - 1):\n",
    "        UserARateSum += UserASongRate[i]    \n",
    "    UserAAverage = UserARateSum / (len(UserASongRate) - 1)\n",
    "    #print('UserAAverage:', UserAAverage)\n",
    "    \n",
    "    UserBRateSum = 0\n",
    "    for i in range(len(UserBSongRate) - 1):\n",
    "        UserBRateSum += UserBSongRate[i]\n",
    "    UserBAverage = UserBRateSum / (len(UserBSongRate) - 1)\n",
    "    #print('UserBAverage:', UserBAverage)\n",
    "    \n",
    "    sum = 0\n",
    "    for i in range(len(UserASongRate) - 1):\n",
    "        sum += (UserASongRate[i] - UserAAverage) * (UserBSongRate[i] - UserBAverage)\n",
    "    numerator = sum\n",
    "    #print('numerator:', numerator)\n",
    "    \n",
    "    sum = 0\n",
    "    for i in range(len(UserASongRate) - 1):\n",
    "        sum += pow((UserASongRate[i] - UserAAverage), 2)\n",
    "    leftPart = np.sqrt(sum)    \n",
    "    \n",
    "    sum = 0\n",
    "    for i in range(len(UserBSongRate) - 1):\n",
    "        sum += pow((UserBSongRate[i] - UserBAverage), 2)\n",
    "    rightPart = np.sqrt(sum)\n",
    "    \n",
    "    denominator = leftPart * rightPart\n",
    "    #print('denominator:', denominator)\n",
    "    \n",
    "    sim = numerator / denominator\n",
    "    \n",
    "    return sim"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sim: nan\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\YuGo\\Anaconda3\\lib\\site-packages\\ipykernel_launcher.py:34: RuntimeWarning: invalid value encountered in double_scalars\n"
     ]
    }
   ],
   "source": [
    "sim = UserFilter(SongRate[0], SongRate[1])\n",
    "print('sim: %.2f'% sim)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sim: nan\n",
      "sim: -0.50\n",
      "sim: -0.50\n",
      "sim: -0.50\n",
      "sim: 0.87\n",
      "sim: -0.50\n",
      "sim: -0.69\n",
      "sim: 0.97\n",
      "sim: -1.00\n",
      "sim: -0.50\n",
      "SimUserID: [61768, 80790]\n",
      "simList: [0.8660254037844385, 0.9707253433941511]\n",
      "SimUserRate: [[3.0, 1.0, 2.0, 5.0], [5.0, 1.0, 2.0, 1.0]]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\YuGo\\Anaconda3\\lib\\site-packages\\ipykernel_launcher.py:34: RuntimeWarning: invalid value encountered in double_scalars\n"
     ]
    }
   ],
   "source": [
    "maxSim = 0.7\n",
    "simList = []\n",
    "SimUserID = []\n",
    "SimUserRate = []\n",
    "\n",
    "\n",
    "for i in range(1, len(SongRate)):\n",
    "    sim = UserFilter(SongRate[0], SongRate[i])\n",
    "    print('sim: %.2f'% sim)\n",
    "    if sim > maxSim:\n",
    "        simList.append(sim)\n",
    "        SimUserID.append(UserRateID[i])\n",
    "        SimUserRate.append(SongRate[i])\n",
    "            \n",
    "print(\"SimUserID:\", SimUserID)\n",
    "print(\"simList:\", simList)\n",
    "print(\"SimUserRate:\", SimUserRate)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 预测最后一个歌曲的分数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [],
   "source": [
    "def predict(UserASongRate, SimUserID, SimUserRate, simList):\n",
    "    \n",
    "    result = 0\n",
    "    SimUserAverage = []\n",
    "    \n",
    "    UserARateSum = 0\n",
    "    for i in range(len(UserASongRate) - 1):\n",
    "        UserARateSum += UserASongRate[i]    \n",
    "    UserAAverage = UserARateSum / (len(UserASongRate) - 1)\n",
    "    \n",
    "    for i in range(len(SimUserRate)):\n",
    "        UserBRateSum = 0\n",
    "        for j in range(len(SimUserRate[0]) - 1):\n",
    "            UserBRateSum += SimUserRate[i][j]\n",
    "        UserBAverage = UserBRateSum / (len(SimUserRate[0]) - 1)\n",
    "        SimUserAverage.append(UserBAverage)\n",
    "    \n",
    "    #print(SimUserRate)\n",
    "    #print(SimUserRate[1][len(SimUserRate[1]) - 1])\n",
    "    #print(len(simList))\n",
    "    #print(len(SimUserRate))\n",
    "    #print(len(SimUserAverage))\n",
    "    \n",
    "    sum = 0\n",
    "    for i in range(len(SimUserRate)):\n",
    "        sum += simList[i] * (SimUserRate[i][len(SimUserRate[0]) - 1] - SimUserAverage[i])\n",
    "    numerator = sum\n",
    "    \n",
    "    sum = 0\n",
    "    for i in range(len(simList)):\n",
    "        sum += simList[i]\n",
    "    denominator = sum\n",
    "    \n",
    "    result = UserAAverage + numerator / denominator\n",
    "    \n",
    "    return result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "SongRate predic: 2.8669934614347676\n",
      "SongRate real: 1.0\n"
     ]
    }
   ],
   "source": [
    "predicRate = predict(SongRate[0], SimUserID, SimUserRate, simList)\n",
    "print('SongRate predic:',predicRate)\n",
    "print('SongRate real:',SongRate[0][len(SongRate[0]) - 1])"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
